﻿using MongoDB.Bson;
using MongoDB.Driver;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.Entities.SustainableProducts;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;

namespace Sustainalytics.ClientDW.Synchronization.SyncProductInvolvement
{
    public class PISolutionsSyncStep : ISyncStep
    {
        private SyncContext _syncContext;
        private ILogger _logger;

        public SyncStepOrder ExecutionOrder => SyncStepOrder.PISolutions;

        private readonly TimeSpan _hBaseWaitRetryDuration = TimeSpan.FromSeconds(int.Parse(ConfigurationManager.AppSettings["HBaseWaitRetryDuration"]));
        private readonly int _hBaseRetryNumberOfTimes = int.Parse(ConfigurationManager.AppSettings["HBaseRetryNumberOfTimes"]);

        public SyncContext Start(SyncContext syncContext)
        {
            _syncContext = syncContext;
            _logger = new SyncContextLogger(syncContext);
            try
            {
                _syncContext.Token.ThrowIfCancellationRequested();
                _syncContext.Log("Begining PISolutions data synchronization");

                SyncPISolutionsData();

                _syncContext.Token.ThrowIfCancellationRequested();
                _syncContext.Log("Ending PISolutions data synchronization");

                _syncContext.Status = SyncOperationStatus.Succeeded;
            }
            catch (AggregateException ex)
            {
                _syncContext.Log(ex.Flatten().ToString());
                _syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }
            catch (Exception ex)
            {
                _syncContext.Log(ex.ToString());
                _syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }

            return _syncContext;
        }

        private void SyncPISolutionsData()
        {
            var (companiesInProgress, solutionList, solutionIndicatorList) = ReadData();

            SaveData(solutionList, solutionIndicatorList);

            UpdateResearchStates(companiesInProgress, solutionList);
        }

        private (List<Guid> CompaniesInProgress, List<SustainableProductsData> SolutionList, List<SustainableProductsFramework> SolutionIndicatorList) ReadData()
        {
            var sw = Stopwatch.StartNew();

            var cddbData = Retry.Do(() => PICddb.GetInstance(SharedEntities.Interfaces.Enums.ResearchFramework.Solutions)
                           .GetPICompanies(),_hBaseWaitRetryDuration,_logger, _hBaseRetryNumberOfTimes);

            sw.Stop();
            _syncContext.Log($"{cddbData.Count} CDDB entities retrieved. Elapsed time: {sw.Elapsed}");

            sw.Restart();

            var storedProcedure = "researchedCompaniesSnapshot_Solutions";
            var storedProcedureCompanyList = "researchedCompanyList_Solutions";
            var solutionStoredProcedure = "getSustainableProductsIndicatorData";

            var data = Retry.Do(() => PISqlRepository.GetInstance(cddbData, storedProcedure, storedProcedureCompanyList, null, solutionStoredProcedure)
                       .GetSolutionsData(), _hBaseWaitRetryDuration,_logger, _hBaseRetryNumberOfTimes);

            sw.Stop();
            _syncContext.Log($"{data.SolutionList.Count + data.SolutionIndicatorList.Count} SustainableProducts entities retrieved from SQL. Elapsed time: {sw.Elapsed}");

            return data;
        }

        private void SaveData(List<SustainableProductsData> assesmentList, List<SustainableProductsFramework> solutionIndicatorList)
        {
            var sw = Stopwatch.StartNew();

            var solutionIndicatorsCollection = GetCollection<SustainableProductsFramework>(ConnectionStringType.ProductInvolvementDW.ToString());

            solutionIndicatorsCollection.Database.DropCollection(typeof(SustainableProductsFramework).Name);

            solutionIndicatorsCollection.InsertMany(solutionIndicatorList);

            var rowsAffected = solutionIndicatorsCollection.Count(new BsonDocument());

            sw.Stop();
            _syncContext.Log($"{rowsAffected} SustainableProductsIndicators entities saved. Elapsed time: {sw.Elapsed}");

            sw.Restart();

            var assesmentCollection = GetCollection<SustainableProductsData>(ConnectionStringType.ProductInvolvementDW.ToString());

            assesmentCollection.Database.DropCollection(typeof(SustainableProductsData).Name);

            assesmentCollection.InsertMany(assesmentList);

            rowsAffected = assesmentCollection.Count(new BsonDocument());

            sw.Stop();
            _syncContext.Log($"{rowsAffected} SustainableProducts entities saved. Elapsed time: {sw.Elapsed}");
        }

        private void UpdateResearchStates(List<Guid> companiesInProgress, List<SustainableProductsData> assesmentList)
        {
            var sw = Stopwatch.StartNew();

            var corporateDataCollection = GetCollection<CorporateData>(ConnectionStringType.ClientDW.ToString());

            var companyProfileResearchStateCollection = GetCollection<CompanyProfileResearchState>(ConnectionStringType.ClientDW.ToString());

            var count = 0;
            foreach (var companyId in companiesInProgress)
            {
                if (assesmentList.Any(x => x.Id == companyId))
                    continue;

                var corporateFilter = Builders<CorporateData>.Filter.Eq(x => x.Id, companyId)
                    & Builders<CorporateData>.Filter.Eq(x => x.SolutionsResearchState.ResearchState, ResearchState.ResearchAvailable);

                if (corporateDataCollection.Count(corporateFilter) != 1)
                    continue;

                var corporateUpdate = Builders<CorporateData>.Update.Set(x => x.SolutionsResearchState.ResearchState, ResearchState.ResearchInProgress)
                                                                     .Set(x => x.SolutionsResearchState.HasResearch, false);
                corporateDataCollection.UpdateOne(corporateFilter, corporateUpdate);

                var profileFilter = Builders<CompanyProfileResearchState>.Filter.Eq(x => x.Id, companyId);
                var profileUpdate = Builders<CompanyProfileResearchState>.Update.Set(x => x.SolutionsResearchState.ResearchState, ResearchState.ResearchInProgress)
                                                                                .Set(x => x.SolutionsResearchState.HasResearch, false);
                companyProfileResearchStateCollection.UpdateOne(profileFilter, profileUpdate);

                _syncContext.Log($"ResearchState set to ResearchInProgress for company {companyId.ToString()}");
                count++;
            }

            sw.Stop();
            _syncContext.Log($"{count} companies found with no SQL data have been set to ResearchInProgress. Elapsed time: {sw.Elapsed}");
        }

        private IMongoCollection<T> GetCollection<T>(string database) where T : IEntity
        {
            var connectionString = String.Empty;

            if (!_syncContext.GAConnectionStrings.TryGetValue(database, out connectionString))
                throw new ArgumentException($"No connection string found for {database}!");

            return MongoFactory.GetCollectionFromDatabase<T>(connectionString);
        }
    }
}